/*****************************************************************************
 * $LastChangedDate: 2011-08-20 12:07:38 -0400 (Sat, 20 Aug 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   HUD text objects.
 * @remarks
 * - In OSG, multiple lines of text is drawn by attaching nodes of
 *   osg::Text objects, contained in Geodes, that define different
 *   2D positions and strings.  These are abstracted by the HudText class.
 * - HudText is auto-drawn by OSG since the HUD is comprised of a camera with
 *   a 2D projection matrix and osg::Text objects which define a line of text.
 *//*
 * LEGAL:   COPYRIGHT (C) 2007 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define HUD_HUD_TEXT_CC 1
#include "base/module.hh"
#include "base/conf.hh"
#include "base/timer.hh"
using namespace base;
#include "math/module.hh"
#include "math/types_trig.hh"
#include "math/defs_trig.hh"
#include "math/funcs.hh"
#include "math/quaternion.hh"
using namespace math;
#include "gfx/module.hh"
#include "gfx/osg.hh"
using namespace gfx;
#include "graph/module.hh"
using namespace graph;
#include "view/module.hh"
#include "view/view.hh"
using namespace view;
#include "hud/module.hh"
#include "hud/hud_text.hh"
#include "hud/font.hh"


namespace hud {

INTERN const fp           HUD_TEXT_COLOR_ALPHA = 1.0f;
INTERN const osg::Vec4f   HUD_TEXT_COLOR( 0.8f, 1.0f, 0.8f, HUD_TEXT_COLOR_ALPHA );
INTERN const Milliseconds HUD_TEXT_ANIMATION_FLIP_MILLISECONDS( 2000 );

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  HudText::Args  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor.
 *****************************************************************************/
HudText::Args::Args( void )
:   mText(),
    mPos(0,0),
    mFont(GET_FONT_MAP().GetDefaultFont()),
    mOrientation(eOrientation_BOTTOM_LEFT),
    mColor(HUD_TEXT_COLOR),
    mOffset(0,0),
    mAnimation(eAnimation_NONE)
{
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////  HudText::Animation struct  ////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor.
 *****************************************************************************/
HudText::Animation::Animation( const HudText::Args& args )
:   mAnimation(args.mAnimation),
    mStartMilliseconds(GET_TIMER().GetElapsedTime()),
    mFlipRotation(0.0f)
{
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  HudText  ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
HudText::HudText( const Args& args )
:   Parent(),
    mGeode(new osg::Geode),
    mOsgText(new osgText::Text),
    m3D(args.mOffset),
    mAnimation(args)
{
// Empty mText is ok.
ASSERT( args.mPos[XX] >= 0 and args.mPos[XX] < 50000 );
ASSERT( args.mPos[YY] >= 0 and args.mPos[YY] < 50000 );

    SET_TYPESIG(this,TYPESIG_HUD_TEXT);

    // Compose geode.
    mGeode->addDrawable( mOsgText.get() );

    // Set the position according to orientation/alignment.
    const Vector2 pos = OrientPosition( args );
    mOsgText->setPosition( osg::Vec3( pos[XX], pos[YY], 0.0f ) );

    // Set other text attributes.
    mOsgText->setText( args.mText );
    mOsgText->setFont( args.mFont.GetPathName() );
    mOsgText->setCharacterSize( args.mFont.GetSize(), 1.0f );
    mOsgText->setColor( RGBA2VEC4F(args.mColor) );

    // Initial animation.
    UpdateAnimation( mAnimation.mStartMilliseconds );
}

HudText::~HudText()
{
    INVALIDATE_TYPESIG(this,TYPESIG_HUD_TEXT);
}

/*****************************************************************************
 * (private ctor subroutine)
 * Re-orient the position of a HudText.
 * The input position must be oriented at BOTTOM_LEFT.
 *****************************************************************************/
Vector2
HudText::OrientPosition( const Args& args )
{
CHECK_TYPESIG(this,TYPESIG_HUD_TEXT);

    Vector2 pos = args.mPos;
    const fp fontWidth  = args.mFont.GetWidth();
    const fp fontHeight = args.mFont.GetHeight();

    // Computing Y is straightforward.
    // Computing X for the right-alignment cases,
    // requires accounting for how many chars the text string has.
    switch ( args.mOrientation )
    {
        case eOrientation_BOTTOM_LEFT: default:
        {
            // NOP
          //pos[XX] unchanged
          //pos[YY] unchanged
        }
        break;

        case eOrientation_BOTTOM_RIGHT:
        {
            pos[XX] = HUD_ORTHO_X2 - pos[XX] - fontWidth * args.mText.size();
          //pos[YY] unchanged
        }
        break;

        case eOrientation_TOP_LEFT:
        {
          //pos[XX] unchanged
            pos[YY] = HUD_ORTHO_Y2 - pos[YY] - fontHeight;
        }
        break;

        case eOrientation_TOP_RIGHT:
        {
            pos[XX] = HUD_ORTHO_X2 - pos[XX] - fontWidth * args.mText.size();
            pos[YY] = HUD_ORTHO_Y2 - pos[YY] - fontHeight;
        }
        break;

        case eOrientation_CENTER:
        {
            pos[XX] = HUD_ORTHO_X2 * 0.5f - (fontWidth * args.mText.size() * 0.5f);
            pos[YY] = HUD_ORTHO_Y2 * 0.5f + pos[YY];  // client may add Y offset
        }
        break;
    }

    return pos;
}

/*****************************************************************************
 * Change the value of HudText.
 *****************************************************************************/
void
HudText::SetText( const string& text )
{
CHECK_TYPESIG(this,TYPESIG_HUD_TEXT);

    mOsgText->setText( text.c_str() );
}

void
HudText::SetText( const char text[] )
{
CHECK_TYPESIG(this,TYPESIG_HUD_TEXT);

    mOsgText->setText( text );
}

/*****************************************************************************
 * Set position by transforming 3D vertex into 2D.
 *****************************************************************************/
void
HudText::SetPosition3D( const WorldVertex& pos3 )
{
CHECK_TYPESIG(this,TYPESIG_HUD_TEXT);

    // Deferred to Update().
    m3D.mValid    = true;
    m3D.mPos3     = pos3;
    m3D.mLastTime = Milliseconds(0);
}

/*****************************************************************************
 * Set font size.
 *****************************************************************************/
void
HudText::SetFontSize( const fp fontSize )
{
CHECK_TYPESIG(this,TYPESIG_HUD_TEXT);

    mOsgText->setCharacterSize( fontSize, 1.0f );
}

/*****************************************************************************
 * Set color of text.
 *****************************************************************************/
void
HudText::SetColor( const RGBA rgba )
{
CHECK_TYPESIG(this,TYPESIG_HUD_TEXT);

    mOsgText->setColor( RGBA2VEC4F(rgba) );
}

/*****************************************************************************
 * Set drawing text with 4 gradient colors.
 *****************************************************************************/
void
HudText::SetGradientColors4( const TinyArray<4,RGBA>& colors4 )
{
CHECK_TYPESIG(this,TYPESIG_HUD_TEXT);
ASSERT( colors4.size() == 4 );

    mOsgText->setColorGradientCorners( RGBA2VEC4F(colors4[0]),
                                       RGBA2VEC4F(colors4[1]),
                                       RGBA2VEC4F(colors4[2]),
                                       RGBA2VEC4F(colors4[3]) );
    mOsgText->setColorGradientMode( osgText::Text::OVERALL );
}

/*****************************************************************************
 * Rotate text.
 *****************************************************************************/
void
HudText::Rotate( const uint axis, const Radian rad )
{
ASSERT_AXIS3(axis);

    // osg::Text has a quaternion.
    Quaternion quat = mOsgText->getRotation();
    quat.Rotate( axis, rad );
    mOsgText->setRotation( quat );
}

/*****************************************************************************
 * Update callback.
 *****************************************************************************/
void
HudText::Update( View& view )
{
    const Milliseconds elapsedMilliseconds = GET_TIMER().GetElapsedTime();

    // Update 3D position.
    UpdatePosition3D( view, elapsedMilliseconds );

    // Do text animation.
    UpdateAnimation( elapsedMilliseconds );
}

void
HudText::UpdatePosition3D( View& view, const Milliseconds elapsedMilliseconds )
{
    // 3D position:
    // Deferred action of SetPosition3D().
    // Note that osgText::setPosition() is a bit slow,
    // so only call it a few times a second.
    if ( m3D.mValid and (elapsedMilliseconds - m3D.mLastTime > Milliseconds(defs::HUD_FREQ)) )
    {
        // Remember time.
        m3D.mLastTime = elapsedMilliseconds;

        // Don't draw if 3D vertex is behind viewpoint.
        const bool ifZFacing = view.IF_Z_FACING( m3D.mPos3 );
        mGeode->setNodeMask( ifZFacing ? ~0 : 0 );
        if ( ifZFacing )
        {
            // Transform 3D --> 2D.
            Vector2 pos2 = view.Project( m3D.mPos3 );

            // Remap 2D from viewport's dimensions to HUD's dimensions.
            const Rect<int> viewport = view.GetRect();
            pos2[XX] = Remap( pos2[XX], viewport.x2, HUD_ORTHO_X2 );
            pos2[YY] = Remap( pos2[YY], viewport.y2, HUD_ORTHO_Y2 );

            // Adjust so that text isn't over 3D object.
            pos2[XX] += m3D.mOffset2[XX];
            pos2[YY] += m3D.mOffset2[YY];

            // Directly change position of osg::Text.
            mOsgText->setPosition( osg::Vec3( pos2[XX], pos2[YY], 0.0f ) );
        }
    }
}

void
HudText::UpdateAnimation( const Milliseconds elapsedMilliseconds )
{
    const Milliseconds delta = elapsedMilliseconds - mAnimation.mStartMilliseconds;
    ASSERT( delta >= Milliseconds(0) );

    switch ( mAnimation.mAnimation )
    {
        case eAnimation_FLIP:
        {
            if ( delta < HUD_TEXT_ANIMATION_FLIP_MILLISECONDS )
            {
                // Compute 90 degrees of rotation according to elapsed milliseconds.
                const Radian rad( (1.0 - (delta.FPX()) / HUD_TEXT_ANIMATION_FLIP_MILLISECONDS.FPX()) * math::RADIAN_90 );

                // First undo rotation then do new rotation.
                Rotate( AXIS_X, -mAnimation.mFlipRotation );
                Rotate( AXIS_X,  rad );

                // Remember previous rotation to later undo it.
                mAnimation.mFlipRotation = rad;
            }
        }
        break;

        case eAnimation_NONE: default:
        break;
    }
}

} // namespace hud
